﻿using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Data;
using System.Data.OracleClient;
using Estructuras.Estructura;
using Estructuras.Comunes;

namespace Estructuras.Funciones.BaseDatos.Oracle
{
    public static partial class Ejecutar
    {
        /// <summary>
        /// Ejecuta un procedimiento almacenado, empleando los datos de entrada como datos de entrada del procedimiento. 
        /// Los datos de salida del procedimiento son usados como registros de salida.
        /// Realiza un commit cada cierto número veces ejecutado.
        /// </summary>
        /// <param name="entrada">Fichero de entrada de datos</param>
        /// <param name="salida">Fichero de salida de datos</param>
        /// <param name="cadenaConexion">Cadena de conexión con la base de datos</param>
        /// <param name="nombreProcedimiento">Nombre del procedimiento almacenado a ejecutar</param>
        /// <param name="parametrosEntrada">Lista de parámetros de entrada y campo del formato de entrada a emplear</param>
        /// <param name="parametrosSalida">Lista de parámetros de salida y campo del formato de salida en el que almacenar los resultados</param>
        /// <param name="frecuenciaCommit">Número de registros que insertar antes de realizar el commit</param>
        /// <param name="formatoEntrada">Estructura de los datos de entrada</param>
        /// <param name="formatoSalida">Estructura de los datos de salida</param>
        /// <returns>Número de filas procesadas</returns>
        public static long ProcedimientoAlmacenadoParametros(TextReader entrada, TextWriter salida, string cadenaConexion, string nombreProcedimiento,
            Dictionary<string, string> parametrosEntrada, Dictionary<string, string> parametrosSalida, long frecuenciaCommit, Estructura.Estructura formatoEntrada, Estructura.Estructura formatoSalida)
        {
            OracleConnection conn;
            // Abre la conexión con la BBDD
            try
            {
                conn = new OracleConnection(cadenaConexion);
                conn.Open();
            }
            catch (Exception ex)
            {
                throw new Excepcion(Constantes.ErrorAbrirConexionBBDD + ex.Message, Constantes.CodigoErrorAbrirConexionBBDD);
            }
            try
            {
                long filasProcesadas = 0;
                // Asigna el nombre del procedimiento almacenado
                OracleCommand comando = conn.CreateCommand();
                OracleTransaction tran = conn.BeginTransaction();
                comando.CommandType = CommandType.StoredProcedure;
                comando.CommandText = nombreProcedimiento;

                // Por cada línea ejecuta el procedimiento almacenado
                string linea = entrada.ReadLine();
                while (linea != null)
                {
                    if (linea.Length != formatoEntrada.LongitudRegistro)
                        throw new Excepcion(Constantes.ErrorLongitudRegistroYFormatoNoCoinciden, Constantes.CodigoErrorLongitudRegistroYFormatoNoCoinciden);

                    comando.Parameters.Clear();
                    foreach (string nombre in parametrosEntrada.Keys)
                    {
                        OracleParameter parametro = new OracleParameter(nombre,
                            formatoEntrada.obtenerValorCampo(linea, parametrosEntrada[nombre]));
                        parametro.Direction = ParameterDirection.Input;
                        comando.Parameters.Add(parametro);
                    }

                    foreach (string nombre in parametrosSalida.Keys)
                    {
                        OracleParameter parametro = new OracleParameter(nombre, OracleType.VarChar, 255);
                        parametro.Direction = ParameterDirection.Output;
                        comando.Parameters.Add(parametro);
                    }

                    // Ejecuta el procedimiento almacenado
                    try
                    {
                        comando.Transaction = tran;
                        comando.ExecuteNonQuery();
                    }
                    catch (Exception ex)
                    {
                        tran.Rollback();
                        throw new Excepcion(Constantes.ErrorEjecutarProcedimientoParametrosBBDD + ex.Message, Constantes.CodigoErrorEjecutarProcedimientoParametrosBBDD);
                    }

                    string lineaSalida = formatoSalida.nuevoRegistro(linea, formatoEntrada);

                    // Obtiene la salida del procedimiento
                    foreach (string nombre in parametrosSalida.Keys)
                    {
                        string valor = string.Empty;
                        if (comando.Parameters[nombre].Value != null)
                            valor = comando.Parameters[nombre].Value.ToString();
                        formatoSalida.establecerValorCampo(ref lineaSalida, parametrosSalida[nombre], valor);
                    }
                    salida.WriteLine(lineaSalida);

                    filasProcesadas++;
                    linea = entrada.ReadLine();
                    if (frecuenciaCommit > 0 && filasProcesadas % frecuenciaCommit == 0)
                    {
                        tran.Commit();
                        tran = conn.BeginTransaction();
                    }
                }

                if (frecuenciaCommit == 0 || (frecuenciaCommit > 0 && filasProcesadas % frecuenciaCommit != 0))
                    tran.Commit();

                // Cierra la conexión con la BBDD
                try
                {
                    conn.Close();
                    conn.Dispose();
                }
                catch (Exception ex)
                {
                    throw new Excepcion(Constantes.ErrorCerrarConexionBBDD + ex.Message, Constantes.CodigoErrorCerrarConexionBBDD);
                }

                return filasProcesadas;
            }
            catch (Excepcion ex)
            {
                if (conn.State != ConnectionState.Closed)
                {
                    conn.Close();
                    conn.Dispose();
                }
                throw ex;
            }
            catch (Exception ex)
            {
                if (conn.State != ConnectionState.Closed)
                {
                    conn.Close();
                    conn.Dispose();
                }
                throw new Excepcion(Constantes.ErrorEjecutarProcedimientoParametros + ex.Message, Constantes.CodigoErrorEjecutarProcedimientoParametros);
            }
        }
    }
}
